VersionBumper.php

<?php

namespace Tlf\Scrawl;

/**
 * Bumps versions. This implementation may be moved to its own, separate library. Idk.
 * 
 */
class VersionBumper {

    /**
     * Use system git commands to determine current version from the branch name and available tags.
     *
     * You should run `git fetch` before using this for accurate results.
     *
     * @param $bump_rules array<int index_in_version_string, array string_prefixes> string prefixes are literal, except a single asterisk (`*`) will always bump that version number, and should only be present if you want a default case of bumping the last index
     *
     * @return string version like 0.8.0.1 from 0.8.0.0
     */
    public function get_new_version_from_git(array $bump_rules): string {
        $last_commit = exec("git log -1 --pretty=%B");
        $branch = $this->get_current_branch();
        $branch_version = $this->get_branch_version($branch);
        $latest_tag = $this->get_latest_tag($branch_version);

        $new_version = $this->get_new_version($latest_tag, $last_commit, $bump_rules);

        return $new_version;
    }


    /**
     * Get a bumped version number. Returns `$latest_version` if none of the `$bump_rules` apply.
     *
     * Ex: 0.8.0 gets commit 'bugfix: something', and bumps to 0.8.0.1 based on the bump rules provided.
     * Ex: 0.8.0.2 gets commit 'feature: cool stuff', and bumps to 0.8.1.0 based on the bump rules provided.
     * Ex: 0.8.0 gets commit 'notes', and does not bump, so '0.8.0' is returned.
     *
     * @param $latest_version string of numbers separated by periods, like '3.8.1'
     * @param $latest_commit_msg string with a prefix that indicates which portion of the version string to bump (increase)
     * @param $bump_rules array<int index_in_version_string, array string_prefixes> string prefixes are literal, except a single asterisk (`*`) will always bump that version number, and should only be present if you want a default case of bumping the last index
     *
     * @return string representing a new version number. This may be the SAME as $latest_version if no $bump_rules passed.
     */
    public function get_new_version(string $latest_version, string $latest_commit_msg, array $bump_rules): string {

        $index_to_bump = $this->get_index_to_bump($latest_commit_msg, $bump_rules);
        $i = $index_to_bump;

        $parts = explode(".", $latest_version);
        $length = count($parts);
        while (($length = count($parts)) <= $index_to_bump){
            $parts[] = '0';
        }

        if ($i>=0){
            $parts[$i] = ((int)$parts[$i])+1;
            while (++$i < $length){
                $parts[$i] = 0;
            }
        }


        $new_version = implode(".", $parts);

        return $new_version;
    }

    /**
     * @return int index to increase by 1, or -1 if there should not be a version increase.
     */
    public function get_index_to_bump(string $commit_message, array $index_rules): int{

        foreach ($index_rules as $index_to_bump => $prefixes_to_cause_bump){
            foreach ($prefixes_to_cause_bump as $prefix){
                if ($prefix=='*')return $index_to_bump;
                $len = strlen($prefix);
                if (substr($commit_message,0,$len+1) == "$prefix:"){
                    return $index_to_bump;
                }
            }
        }

        return -1;
    }

    /**
     *
     * @param $version_prefix string like "1.0" or "0.8"
     */
    public function get_latest_tag(string $version_prefix=''): string{

        ob_start();
        system("git tag --list \"$version_prefix.*\"");
        $available_versions = ob_get_clean();

        $tags = explode("\n", trim($available_versions));

        //echo implode(" ", $tags);

        $max_bug_version = -1;
        foreach ($tags as $full_version){
            if (trim($full_version)=='')continue;
            $parts = explode('.',$full_version);
            $bugversion = (int)$parts[2];
            if ($bugversion > $max_bug_version) $max_bug_version = $bugversion;
        }

        return trim("$version_prefix.$max_bug_version");
    }

    /**
     * Return the name of the current git branch
     *
     */
    public function get_current_branch(): string {
        return exec("git rev-parse --abbrev-ref HEAD");
    }

    /**
     * Assumes branch name starts with 'v', removes it, and returns the remaining string, assumed to be numbers separated by periods.
     *
     * @return string like 1.0
     */
    public function get_branch_version(string $branch_name): string {
        return substr($branch_name,1);
    }



}